iT邦幫忙

2021 iThome 鐵人賽

DAY 11
0

通道Channel

Channel可以想像成是一種資料結構,可以push data進去也可以pull data出來。

因為Channel會等待另一端完成Push/Pull的動作才會繼續往下處理,而且特性使其可以在Goroutines間同步處理的資料,而不用使用lock, unlock等方式。

Create Channel

這裡示範建立一個int型別的channel

ch := make(chan int)

Push data to Channel

將data d 送入channel ch之中

ch <- d 

Pull data from Channel

將data從channel ch拿出,並賦予給變數d

d := <- ch

Channel是用來讓Goroutine溝通時使用的一種資料結構,並且由於其阻塞的特性,它也能夠當成一種等待goroutine的方法。

Goroutine with Channel

package main

import (
	"fmt"
	"time"
)

func main() {
	ch := make(chan string)

	go calculator(ch)

	time.Sleep(3 * time.Second)
	fmt.Println(<-ch)
	time.Sleep(time.Second)
	fmt.Println("main goroutine finished")
}

func calculator(ch chan string) {
	fmt.Println("Start to calculate goroutines")
	time.Sleep(time.Second)
	fmt.Println("Stop to calculate goroutines")

	ch <- "FINISH"

	fmt.Println("Finish calculator")
}

運行後可得以下結果

Start to calculate goroutines
Stop to calculate goroutines
FINISH
Finish calculator
Main goroutine finished

那我們這邊的三秒延遲目的是為了讓main thread慢於goroutines,一秒延遲則是模擬goroutines的作業時間! 所以他會依序的去進行goroutines作業→打印channel的內容→完成goroutines function→完成main thread。

通道分為兩種,有buffer與無buffer的,也就是有儲存空間限制的channel與無限制的channel。

此外,通道也能分為單向與雙向兩種,就是能傳資料的callervs只有一方能傳資料的callee

Unbuffered channel

Variable := make(chan Type)

chan 被製造出來之後,需要傳入 要被你併發出去的func,它靠這種方式傳資料。

chan是有方向性的,要看箭頭←的方向。

chan <- A `把 A這個東西 塞進chan`
B<- chan  `從chan 挖東西出來 到B`

由下兩範例來說明接收的方向性:

package main

import (
        "fmt"
)

func main() {
	ch := make(chan int)
	go func1(ch)
	ch <- 100
}

func func1(ch chan int) {
	i := <-ch
	fmt.Println(i)
}
package main

import (
        "fmt"
	"time"
)

func main() {
	ch := make(chan int)
	go func2(ch)
	got := <-ch
	fmt.Println(got)
}

func func2(ch chan int) {
	time.Sleep(time.Second * 2)
	ch <- 999
}

運行後可得以下結果

999

由上面例子我們可以得知,Unbuffered Channel有一個特性:

  • Push一筆資料會造成Pusher的等待
  • Pull時沒有資料則會造成Puller的等待

也因此如果Pusher的執行一次時間較Puller短,會造成Pusher被迫等待Puller拉取才能進行下一次的push,而這樣的等待是相當浪費時間的。

為了解決此問題,因此誕生了另一種Channel,Buffered Channel。

Buffered channel

Variable := make(chan Type, Number)

有限制儲存空間的通道,若限制放兩個,就只能有兩筆數據,倘若塞入第三筆的話則會造成死結DeadLock

Deadlock

package main

func main() {
	ch := make(chan int, 2)
	go func3(ch)
	ch <- 100
	ch <- 99

	ch <- 98 // 發生deadlock
}

func func3(ch chan int) {
}

運行後可得以下結果

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.main()
	/tmp/sandbox241095799/prog.go:9 +0xa7

Block vs Deadlock

以上例來說,通常chan塞不下第三筆數據時,只會發生Block(阻塞滯留),而當Block永遠無法解開的情況發生,則是 Deadlock(死結)。上面會發生死結是因為 不論等多久,都不會從Block的狀態中脫離。

只要通道(Chan)塞不下,或者沒東西可挖,都會發生Block阻塞。

Block

以下是通道Channel 阻塞Block的例子

package main

import (
        "fmt"
	"time"
)

func main() {
	ch := make(chan int, 2)
	go func1(ch)
	for i := 0; i < 10; i++ {
		ch <- i
		fmt.Println("main sent", i)
	}
	time.Sleep(time.Second)
}

func func1(ch chan int) {
	for {
		i := <-ch
		fmt.Println("func1 got", i)
		time.Sleep(time.Millisecond * 100)
	}
}

運行後可得以下結果

main sent 0
main sent 1
main sent 2
func1 got 0
func1 got 1
main sent 3
func1 got 2
main sent 4
func1 got 3
main sent 5
func1 got 4
main sent 6
func1 got 5
main sent 7
func1 got 6
main sent 8
func1 got 7
main sent 9
func1 got 8
func1 got 9

主程式不間斷地連續塞十次數字送完休息1秒;而func1每0.1秒吃下來一個數值。

雖然慢,但程式不會打死結,只是會造成執行效率低下而已。

如果把Buffer Size: 2換成5

package main

import (
        "fmt"
	"time"
)

func main() {
	ch := make(chan int, 2)
	go func1(ch)
	for i := 0; i < 10; i++ {
		ch <- i
		fmt.Println("main sent", i)
	}
	time.Sleep(time.Second)
}

func func1(ch chan int) {
	for {
		i := <-ch
		fmt.Println("func1 got", i)
		time.Sleep(time.Millisecond * 100)
	}
}

運行後可得以下結果

main sent 0
main sent 1
main sent 2
main sent 3
main sent 4
main sent 5
func1 got 0
func1 got 1
main sent 6
func1 got 2
main sent 7
func1 got 3
main sent 8
func1 got 4
main sent 9
func1 got 5
func1 got 6
func1 got 7
func1 got 8
func1 got 9

同時間通道裡最多會有五個數字。

塞與取的先後順序,透過log.SetFlags(5)來看會比較清楚。

無緩衝通道不等於無限通道

The buffer size is the number of elements that can be sent to the channel without the send blocking.

Buffer 是拿來緩衝用的,Unbuffered Channel則是0緩衝

,就是沒有緩衝啦!Unbuffered 是需要有同時有一頭寫入、另一頭讀出,才能動的。

那Golang有沒有 無限制的通道(Unlimited)呢?

當然是 沒有!

因為倘若需要給個100000byte大小的Channel必須先挪出100000byte的記憶體空間出來,如此一來多創幾個Unlimited Chaneel就會造成Out Of Memory了!

Summary

這章節我們講述了Channel的用法,Unbuffered與Buffered的差別,彼此所遇到的問題與困難,以及它們各自搭配goroutines的使用範例,


上一篇
Day10 Sync.WaitGroup & Sync.Map
下一篇
Day12 Select
系列文
fmt.Println("從零開始的Golang生活")30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言